<?php
/**
 * WpDatexServer.php
 * 
 * This file contains the WpDatexServer class, an abstract class providing functionality to 
 * adapt WpDatex classes to JavaScript using XmlHTTPRequest to synchronize. This class must be 
 * extended to declare the JavaScript functionality.
 * @author Robert Schmidt <robert@wildpack.de>
 * @version 1.0
 * @package wildpack
 * @subpackage dataaccess.wpdatexserver
 * @dependencies: wildpack.base.observe.*
 */

/**
 * Aa abstract to handle WpDatex usage through JavaScript and AJAX
 * 
 * This class provides functionality to adapt WpDatex classes to JavaScript using 
 * XmlHTTPRequest to synchronize. This class must be extended to declare the JavaScript functionality.
 * 
 * How to setup the server:
 * <code>
 * //wpdserver.php
 * 
 * require_once('path_to_WildPack/WildPack.php');
 * $wp = new WildPack();
 * $wp->using('dataaccess');
 * $wp->using('mvc');
 * $wp->using('web');
 * 
 * //Include all SimpleDatex instances to be used by the server:
 * require_once('Tester.php');
 * require_once('Testers.php');
 * 
 * //-- Database and logging --
 * $l = new WpMemLog();
 * $c = new WpConnectMySQL(array(
 *   'location' => 'localhost/db',
 *   'user' => 'user',
 *   'password' => 'pass',
 *   'log' => $l
 * ));
 * 
 * //-- Web functionality --
 * $req = new WpHttpRequest();
 * $res = new WpHttpResponse();
 * $ses = new WpSimpleSession();
 * $ses->startSession();
 * 
 * //-- Start server
 * $wpds = new WpDatexServerXY($c, $l, $req, $res, $ses, 'wpdserver.php', 'wptest');
 * $wpds->start();
 * </code>
 * 
 * How to access the server:
 * <code>
 * <html>
 * <head><title>WpDatexServerXY test</title></head>
 * <body>
 * 
 * <script type="text/javascript" src="mootools.js"></script>
 * <script type="text/javascript" src="wpdserver.php?js"></script>
 * <script type="text/javascript">
 *    //Now it is possible to access the SimpleDatex classes directly from JS:
 *     var t = new wptest.Testers();
 *     t.load([], '', '', 0, 0, function(t, chk){
 *        console.log('Loaded '+String(t.length())+' datasets.');
 *        t.rewind();
 *        while(t.findNext()) {
 *          console.log('Field value: '+t.get('fieldname'));
 *        }
 *     });
 * </script>
 * </body>
 * </html>
 * </code>
 * 
 * @author Robert Schmidt <robert@wildpack.de>
 * @version 1.0
 * @package wildpack
 * @subpackage dataaccess.wpdatexserver
 * @dependencies: wildpack.base.observe.*
 * @abstract
 */
abstract class WpDatexServer extends WpObservable
{
	/**
	 * @var The list content 
	 */
	private $list = array();
	
	/**
	 * @var The list type (black or white)
	 */
	private $listtype = 'black';
	
	/**
	 * @var The database conenctor
	 */
	protected $c = null;
	
	/**
	 * @var The log object
	 */
	protected $l = null;
	
	/**
	 * @var The request object
	 */
	protected $req = null;
	
	/**
	 * @var The response object
	 */
	protected $res = null;
	
	/**
	 * @var The session object
	 */
	protected $ses = null;
	
	/**
	 * @var The target for JavaScript e.g. "index.php"
	 */
	protected $target = '';
	
	/**
	 * @var The namespace to use for the JavaScript classes
	 */
	protected $ns = 'wildpack';
	
	/**
	 * @var The return array
	 */
	protected $ret = array();
	
	/**
	 * Checks wether it is allowed to use the requested class or not by checking the black or white list 
	 * @param string $class The name of the class to check
	 * @return True if usage of class is allowed, false else
	 */
	protected function isAllowed($class)
	{
		if ($this->listtype === 'black' && in_array($class, $this->list)) {
			return false;
		}
		if ($this->listtype === 'white' && !in_array($class, $this->list)) {
			return false;
		}
		
		return true;
	}
	
	/**
	 * Retrieves the base JavaScript to be created once
	 * @return string The base JavaScript
	 */
	protected abstract function getBaseJs();
	
	/**
	 * Retrieves the JavaScript adaption of the specified SimpleDatexS class
	 * @param string $name The name of the SimpleDatexS class to adapt
	 * @param SimpleDatexS $instance An instance of the class to retrieve structure information
	 * @return string The JavaScript adapation of the specified SimpleDatexS class
	 */
	protected abstract function getJsForDatexS($name, $instance);
	
	/**
	 * Retrieves the JavaScript adaption of the specified wpdatex class
	 * @param string $name The name of the datex class to adapt
	 * @param SimpleDatexS $instance An instance of the class to retrieve structure information
	 * @return string The JavaScript adapation of the specified wpdatex class
	 */
	protected abstract function getJsForDatexM($name, $instance);
	
	/**
	 * Retrieves wether the requested class is of SimpleDatexS or SimpleDatexM
	 * @return unknown_type
	 */
	protected function _getType()
	{
		$cls = $this->req->getParam('cls');
		
		if (is_null($cls) || $cls === '') {
			return '';
		}
		
		$r = new ReflectionClass($cls);
		if ($r->isSubclassOf('SimpleDatexS')) {
			return 'SimpleDatexS';
		}
		if ($r->isSubclassOf('SimpleDatexM')) {
			return 'SimpleDatexM';
		}
		
		return '';
	}
	
	/**
	 * The class constructor
	 * @param WpConnectBase $c The connector to use for database access
	 * @param IWpLog $l The log for logging errors (this log will also be forwarded to the WpDatex instances)
	 * @param IWpRequest $req The request object
	 * @param IWpResponse $res The response object
	 * @param IWpSession $ses The session object, used to store object states
	 * @param string $target The target of the JavaScript ajax calls
	 * @param string $ns The namespace, the JavaScript classes should be created in
	 */
	public function __construct($c, $l, $req, $res, $ses, $target, $ns='')
	{
		$this->c = $c;
		$this->l = $l;
		$this->req = $req;
		$this->res = $res;
		$this->ses = $ses;
		$this->ns = $ns;
		$this->target = $target;
		
		$this->addEvents(array(
			'preinvoke', 'postinvoke', 'error'
		));
	}
	
	/**
	 * Creates the JavaScript adapter classes
	 */
	public function createJsClasses()
	{
		$js = $this->getBaseJs();
		
		foreach(get_declared_classes() as $cls) {
			$r = new ReflectionClass($cls);
			if ($r->isSubclassOf('SimpleDatexS') && !$r->isAbstract()) {
			
				$o = $r->newInstanceArgs(array(
					$this->c, $this->l
				));
				
				$js .= $this->getJsForDatexS($cls, $o);
				continue;
			}
			if ($r->isSubclassOf('SimpleDatexM') && !$r->isAbstract()) {
			
				$o = $r->newInstanceArgs(array(
					$this->c, $this->l
				));
				
				$js .= $this->getJsForDatexM($cls, $o);
				continue;
			}
		}
		
		$this->res->addHeader('Content-type', 'text/javascript');
		$this->res->write($js);
		$this->res->flush();
	}
	
	/**
	 * Retrieves the instance of a specified stateid. If no stateid specified, a new instance is
	 * created and returned
	 * @return object A class instance or null
	 */
	public function getInstance()
	{
		$cls = $this->req->getParam('cls');
		$stateid = $this->req->getParam('stateid');
		
		if (is_null($cls) || $cls === '') {
			return null;
		}
		
		//-- Check lists--
		if (!$this->isAllowed($cls)) {
			$this->ret['error'] = 'Method is not allowed to be invoked.';
			return null;
		}
		
		$r = new ReflectionClass($cls);
		$o = $r->newInstanceArgs(array(
			$this->c, $this->l
		));
		
		if (is_null($stateid) || $stateid === '') {
			$this->ret['stateid'] = date('YmdHis').strval(rand());
			return $o;
		}
		
		//-- Loaded stated object --
		$d = $this->ses->get('wpds_'.$stateid);
		if (is_null($d)) {
			$this->ret['error'] = 'No data found for a valid stateid.';
			return null;
		}
		
		$this->ret['stateid'] = $stateid;
		
		if ($r->isSubclassOf('SimpleDatexS')) {
			$o->_isNew = $d['_isNew'];
		}
		if ($r->isSubclassOf('SimpleDatexM')) {
			$o->Index = $d['index'];
		}
		
		$o->Data = $d['data'];
		$o->Loaded = $d['loaded'];
		
		return $o;
	}
	
	/**
	 * Invokes the method of the provided object
	 * @param SimpleDatexS $o The object to invoke the method of
	 */
	public function invokeMethod($o)
	{
		$m = $this->req->getParam('method'); 
		$ps = $this->req->getParam('param');
		
		if ($ps !== null) {
			$p = json_decode($ps, true);
		} else {
			$p = array();
		}
    $p = is_null($p) ? array() : $p;
		
		if (!method_exists($o , $m)) {
			$this->ret['error'] = 'Method not found in instance.';
			return;
		}
    
    if (!is_array($p)) {
      $this->ret['error'] = 'Provided parameters are not of type array.';
      return;
    }
		
		if ($this->fireEvent('preinvoke', array($this, $o, $m, $p)) === false) {
			$this->ret['error'] = 'Method call has been stopped by event handler.';
			return;
		}
    $ret = call_user_func_array(array($o, $m), $p);
		$this->ret['returnvalue'] = $ret;
		$this->fireEvent('preinvoke', array($this, $o, $m, $p, $ret));
	}
	
	/**
	 * Define a blacklist (an already defined white list will become invalid) 
	 * @param array $list An array of classes to ignore
	 */
	public function setBlacklist($list)
	{
		if (!is_array($list)) {
			$this->l->logError($loc, '', 'Invalid parameter', 'Parameter is not of type array.', '0');
			die('System error. This incident has been reported.');
		}
		
		$this->list = $list;
		$this->listtype = 'black';
	}
	
	/**
	 * Define a whitelist (an already defined blacklist will become invalid) 
	 * @param array $list An array of classes to allow (all others will be ignored) 
	 */
	public function setWhitelist($list)
	{
		if (!is_array($list)) {
			$this->l->logError($loc, '', 'Invalid parameter', 'Parameter is not of type array.', '0');
			die('System error. This incident has been reported.');
		}
		
		$this->list = $list;
		$this->listtype = 'white';
	}
	
	/**
	 * Starts the server
	 */
	public function start()
	{
		$js = $this->req->getParam('js');
		if (!is_null($js)) {
			$this->createJsClasses();
			return;
		}
		
		$this->ret = array(
			'data' => array(),
			'loaded' => false,
			'error' => '',
			'stateid' => '',
			'returnvalue' => null
		);
		
		$t = $this->_getType();
		
		$o = $this->getInstance();
		if (is_null($o)) {
			$this->ret['error'] = 'Instance could not be created.';
			$this->res->write(json_encode($this->ret));
			$this->res->flush();
			return;
		}
		
		$this->invokeMethod($o);
		
		//--Store object data --
		$this->ret['data'] = $o->Data;
		$this->ret['loaded'] = $o->Loaded;
		$ses = array(
			'data' => $o->Data,
			'loaded' => $o->Loaded
		);
		
		if ($t === 'SimpleDatexS') {
			$this->ret['_isNew'] = $o->_isNew;
			$ses['_isNew'] = $o->_isNew;
		}
		if ($t === 'SimpleDatexM') {
			$this->ret['index'] = $o->Index;
			$ses['index'] = $o->Index;
		}
		
		//--Store object data to session--
		$this->ses->set('wpds_'.$this->ret['stateid'], $ses);
		
		$this->res->write(json_encode($this->ret));
		$this->res->flush();
	}
}

/* End of file WpDatexServer.php */
/* Location: ./wildpack/dataaccess/wpdatexserver/WpDatexServer.php */